Skip to main content

Studies

A Study is the complete definition of one backtesting experiment. It records what is being evaluated, the market universe, the time windows, the engine, and the assumptions needed to interpret the results.

Studies are natively represented in the Open Backtest Format (.obtf). When a backtest is saved, its study definitions, runs, summaries, execution settings, and Monte Carlo results can travel together in one reproducible bundle.

What a study contains

FieldPurpose
nameStable, unique name within the parent backtest.
descriptionHuman-readable explanation of the experiment.
universeThe symbols, quote currency, and market being evaluated.
backtest_windowsTraining and optional test periods to execute.
enginesThe event-driven or vector engine to use.
initial_capitalStarting capital for the evaluation.
risk_free_rateAnnualized rate used by risk-adjusted metrics.
sample_typeThe study's role, such as in-sample or walk-forward.
window_partWhether to run the train, test, or both parts of each window.
metadataCustom provenance, parameter fingerprints, or experiment tags.

Each study targets exactly one universe. To test the same strategy on a different set of assets, create another study. Multiple studies can be stored in the same backtest bundle, making comparisons explicit without mixing their evidence.

Define a study

from datetime import datetime, timezone

from investing_algorithm_framework import (
BacktestDateRange,
BacktestEngine,
BacktestWindow,
Study,
StudySampleType,
Universe,
WindowPart,
)

study = Study(
name="btc_eth_walk_forward",
description="Quarterly validation of the momentum strategy",
universe=Universe(
key="crypto_majors",
symbols=["BTC/EUR", "ETH/EUR"],
trading_symbol="EUR",
market="BITVAVO",
),
backtest_windows=[
BacktestWindow(
name="2024_q1",
train_range=BacktestDateRange(
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2024, 1, 1, tzinfo=timezone.utc),
),
test_range=BacktestDateRange(
start_date=datetime(2024, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2024, 4, 1, tzinfo=timezone.utc),
),
warmup_days=30,
fold_index=0,
),
],
engines=[BacktestEngine.VECTOR],
initial_capital=10_000,
risk_free_rate=0.027,
sample_type=StudySampleType.WALK_FORWARD,
window_part=WindowPart.TEST,
metadata={"strategy_family": "momentum"},
)

backtests = app.run_backtest(strategy=strategy, study=study)

The engines list selects the requested runner. The framework stores completed runs in per-engine result slots, so vector and event-driven evidence remain separate inside the same study definition.

Universes

The Universe is part of the study, not an external filter applied after the run. It identifies the exact assets and market context used to produce the evidence. See Universes for field definitions, stable keys, and universe out-of-sample patterns.

Use one study per universe when performing universe out-of-sample validation:

major_assets = Study(
name="momentum_majors",
universe=Universe(
key="majors",
symbols=["BTC/EUR", "ETH/EUR"],
trading_symbol="EUR",
market="BITVAVO",
),
sample_type=StudySampleType.IN_SAMPLE,
backtest_windows=windows,
)

held_out_assets = Study(
name="momentum_held_out_assets",
universe=Universe(
key="held_out_assets",
symbols=["SOL/EUR", "ADA/EUR"],
trading_symbol="EUR",
market="BITVAVO",
),
sample_type=StudySampleType.OUT_SAMPLE_UNIVERSE,
backtest_windows=windows,
)

Windows and folds

A BacktestWindow contains a required train_range and an optional test_range. This supports simple holdouts, rolling or anchored windows, walk-forward folds, and time-based out-of-sample studies. See Backtest Windows for detailed patterns and warmup behavior.

window_part controls which ranges are executed:

  • WindowPart.TRAIN runs only training ranges.
  • WindowPart.TEST runs test ranges, falling back to the training range when a window has no test range. This is the default.
  • WindowPart.BOTH runs both ranges as separate runs.

Use warmup_days for indicator initialization and fold_index to preserve a fold's identity in rolling and walk-forward studies.

Sample types

StudySampleType labels the role a study plays in the research process. Built-in types include in-sample, time and universe out-of-sample, walk-forward, stress, Monte Carlo, and exploratory studies. Custom string values are also supported, so downstream tools should treat unknown values as valid experiment labels.

Inspect results

After execution, retrieve the study from its parent Backtest and inspect the engine-specific runs or summaries:

backtest = next(backtests.iter_backtests())
completed_study = backtest.get_study("btc_eth_walk_forward")

runs = completed_study.get_runs(engine="vector")
summary = completed_study.get_summary(engine="vector")
window_metrics = completed_study.get_metrics(
engine="vector",
backtest_window=completed_study.backtest_windows[0],
)

Use copy_definition() to rerun the same universe, windows, and assumptions without carrying over existing runs or summaries:

replay_study = completed_study.copy_definition()
replay_study.engines = [BacktestEngine.EVENT_DRIVEN]

event_backtests = app.run_backtest(strategy=strategy, study=replay_study)

This makes vector-to-event validation reproducible and keeps both experiments grounded in the same study definition.